use std::{borrow::Cow, ops::Deref};

use axum::{
    body::Bytes,
    extract::{FromRequest, Request},
    http::{header, HeaderValue, StatusCode},
    response::{IntoResponse, Response},
};
use serde::{de::DeserializeOwned, Serialize};

use crate::error::AppErr;

#[repr(transparent)]
pub struct Cbor<T>(pub T);

#[derive(Debug, Serialize)]
pub struct ErrInfo {
    err_code: i32,
    err_msg: Cow<'static, str>,
}

const HEADER_RESP: &'static str = "resp";
const HEADER_VALUE_RESP_OK: HeaderValue = HeaderValue::from_static("ok");
const HEADER_VALUE_RESP_FAIL: HeaderValue = HeaderValue::from_static("fail");
const HEADER_VALUE_RESP_PANIC: HeaderValue = HeaderValue::from_static("panic");

const CONTENT_TYPE_BINARY: HeaderValue = HeaderValue::from_static("application/octet-stream");
const CONTENT_TYPE_JSON: HeaderValue = HeaderValue::from_static("application/json");

#[inline(always)]
fn make_response<F: FnOnce() -> Result<Response, AppErr>>(cb: F) -> Response {
    match cb() {
        Ok(resp) => resp,
        Err(_) => {
            let mut resp = StatusCode::OK.into_response();
            resp.headers_mut()
                .insert(HEADER_RESP, HEADER_VALUE_RESP_PANIC);
            resp
        }
    }
}

impl<T> Deref for Cbor<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: DeserializeOwned, S: Send + Sync> FromRequest<S> for Cbor<T> {
    type Rejection = AppErr;

    async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
        let body = Bytes::from_request(req, state).await?;
        let value = serde_cbor::from_slice(&body)?;
        Ok(Cbor(value))
    }
}

pub struct Void;

impl IntoResponse for Void {
    fn into_response(self) -> Response {
        make_response(|| {
            let mut resp = StatusCode::OK.into_response();
            resp.headers_mut().insert(HEADER_RESP, HEADER_VALUE_RESP_OK);
            Ok(resp)
        })
    }
}

impl<T: Serialize> IntoResponse for Cbor<T> {
    fn into_response(self) -> Response {
        make_response(|| {
            let body = serde_cbor::to_vec(&self.0)?;
            let mut resp = body.into_response();
            resp.headers_mut().insert(HEADER_RESP, HEADER_VALUE_RESP_OK);
            resp.headers_mut()
                .insert(header::CONTENT_TYPE, CONTENT_TYPE_BINARY);
            *resp.status_mut() = StatusCode::OK;
            Ok(resp)
        })
    }
}

impl IntoResponse for AppErr {
    fn into_response(self) -> Response {
        make_response(|| {
            let msg = self.to_string();
            let err_info = ErrInfo {
                err_code: -1,
                err_msg: Cow::Owned(msg),
            };
            let mut resp = serde_cbor::to_vec(&err_info)?.into_response();
            resp.headers_mut()
                .insert(HEADER_RESP, HEADER_VALUE_RESP_FAIL);
            resp.headers_mut()
                .insert(header::CONTENT_TYPE, CONTENT_TYPE_BINARY);
            *resp.status_mut() = StatusCode::OK;

            Ok(resp)
        })
    }
}
